<template>
  <div class="upload-input">
    <el-input v-model="inputText" :disabled="true" class="upload-input-text" placeholder="点击选择文件"></el-input>
    <!--  自定义点击上传 -->
    <input v-show="false" ref="fileRef" multiple type="file" @change="fileChange">
    <el-button class="upload--button" type="file" @click="uploadFile">上传文件</el-button>
    <!--    清除所有文件-->
    <el-button plain @click="clearFiles" style="font-size: 1rem">清除全部</el-button>
  </div>
  <!--elementplus自带upload上传组件，主要属性：
  ref：html内容的引用，
  http-request：上传文件请求入口
  file-list：上传文件列表
  on-success：文件成功上传后逻辑
  headers：文件默认携带请求头
  before-upload：文件上传前逻辑
  multiple：打开多文件上传
  -->
  <el-upload
      @pointerdown.stop
      ref="upload"
      class="upload-demo"
      action="/"
      :http-request="handleHttpRequest"
      :auto-upload="true"
      v-model:file-list="fileList"
      :on-success="handleSuccess"
      :before-upload="handleBeforeUpload"
      multiple
  >
    <template #default>
      <!--     文件上传列表墙 -->
      <ul class="el-upload-list el-upload-list--picture-card" ref="drag" v-show="fileList.length!==0" @click.stop>
        <!--        遍历上传列表-->
        <li v-for="file in fileList" :key="file.name"
            class="upload-list__item" @pointerdown.stop>
          <div class="upload-template">
            <div
                v-show="computeFile(file,'photoUploading')">
              <img class="upload-list__item-thumbnail imageType"
                   v-show="file.response"
                   :src="file.response?.data?.fileUrl">
              <el-empty class="upload-list__item-thumbnail imageType" v-show="!file.response"
                        description="图片正在上传"></el-empty>
            </div>
            <component class="upload-list__item-thumbnail noImageType"
                       v-show="computeFile(file,'noImage')"
                       :is="fileTypeMap(file.name.substring(file.name.lastIndexOf('.')+1))"></component>
            <label class="upload-list__item-status-label"
                   :style="computeStatus(file.status)">
              <i
                  class="el-icon  el-icon--check">
                <transition name="bounce">
                  <SuccessArrowIcon class="tick" v-if="file?.status==='success'"></SuccessArrowIcon>
                </transition>
              </i>
            </label>
            <!--            上传进度条-->
            <transition name="bounce2" mode="out-in">
              <el-progress class="progress" v-if="computePercent(file.uid)!==100" :percentage="computePercent(file.uid)"
                           :stroke-width="12" :text-inside="true"
                           :show-text="false"
                           :color="computeProgress(file.uid)">
              </el-progress>
              <el-tag effect="dark" type="success" round class="progress-complete" v-else>已完成</el-tag>
            </transition>
            <!--文件信息-->
            <div class="upload-text">
              <!--文件名-->
              <div class="file-name-text" v-text="file.name"></div>
              <!--文件大小-->
              <div class="file-size"
                   v-text="(file.size/1024/1024<1?(file.size/1024).toFixed(2)+'K':(file.size/1024/1024).toFixed(2)+'M')"></div>
            </div>
            <span class="el-upload-list__item-actions">
          <!--图片预览按钮-->
          <span
              v-if="computeFile(file,'photoPreview')"
              class="upload-list__item-preview"
              @click.stop="handlePreview(file.response?.data?.fileUrl,'image')"
          >
            <el-icon><UploadShowIcon></UploadShowIcon></el-icon>
            <!--视频预览按钮-->
          </span>
              <span
                  v-if="computeFile(file,'videoPreview')"
                  class="upload-list__item-preview"
                  @click.stop="handlePreview(file.response?.data?.fileUrl,'video')"
              >
            <el-icon><UploadShowIcon></UploadShowIcon></el-icon>
          </span>
              <!--删除文件按钮-->
          <span
              v-if="!disabled"
              class="upload-list__item-delete"
              @click="handleRemove(file)"
          >
            <el-icon><UploadDeleteIcon/></el-icon>
          </span>
        </span>
          </div>
        </li>
      </ul>
      <!--      文件上传空状态-->
      <div class="empty-file" ref="empty" v-show="fileList.length===0" @click.stop>
        <el-icon class="el-icon--upload">
          <UploadFilled/>
        </el-icon>
        <!--        空状态的展示文本信息-->
        <div class="el-upload__text">
          请上传文件，支持多文件上传，文件限制大小为{{ uploadSize }}M，允许格式有docx,xlsx,pptx,pdf,xmind,jpg,png,mp4
        </div>
      </div>
    </template>
  </el-upload>
  <!--  文件预览弹窗-->
  <el-dialog width="700" :model-value="visible" @closed="closeImageDialog">
    <el-image v-if="previewType==='image'" w-full :src="previewUrl" alt="Preview Image"/>
    <PreviewVideo v-if="previewType==='video'" :videoUrl="previewUrl"/>
  </el-dialog>
</template>

<script setup>
import {computed, nextTick, onBeforeUnmount, onMounted, ref, watchEffect} from 'vue'
import {ElMessage} from 'element-plus'
import {getToken} from '@/utils/auth'
import UploadShowIcon from '@/components/common/UploadShowIcon'
import UploadDeleteIcon from '@/components/common/UploadDeleteIcon'
import SuccessArrowIcon from '@/components/common/SuccessArrowIcon'
import PreviewVideo from '@/components/common/PreviewVideo'
import request from "@/api/request";
import useComputedFileType from "@/hook/useComputedFileType";
//父组件传入参数，可自定义
const props = defineProps({
  type: String
})
//本组件向外触发事件，分别为 单次上传成功事件、移除上传文件事件、正在上传事件、所有文件上传成功事件
const emit = defineEmits(['uploadSuccess', 'removeUpload', 'onProgress', 'allUploadSuccess'])
// element上传html引用
const upload = ref()
// 点击上传文件引用
const fileRef = ref()
const disabled = ref(false)
// 文件后缀类型
const fileType = ref('')
// 上传文件列表
const fileList = ref([])
const drag = ref(null)
const empty = ref(null)
//文件限制大小
const uploadSize = ref(70)
//文件预览类型
const previewType = ref('')
//文件上传中的数量
const uploadingNum = ref(0)
// 文件上传进度列表
const uploadPercentList = ref([])
// 文件上传定时器列表
const uploadTimerList = ref([])

// 上传进度推进定时器
const createTimer = (uid, timeoutSize) => {
  let currentTimerPercent = null
  // 获取当前文件的进度
  uploadPercentList.value.forEach(item => {
    if (item.fileUid === uid) {
      currentTimerPercent = item
    }
  })
  // 开启定时器
  const timer = setInterval(() => {
    // 如果进度小于199，每次增加1
    if (currentTimerPercent.percent < 199) {
      currentTimerPercent.percent += 1
    }
  }, timeoutSize * 10)
  // 将定时器存入列表
  uploadTimerList.value.push({uid: uid, timer: timer})
}

// 计算总进度
const computePercent = computed(() => {
  return (fileUid) => {
    let upload = 0
    uploadPercentList.value.forEach(item => {
      if (item.fileUid === fileUid) {
        upload = item.percent
      }
    })
    return (upload) / 2 | 0
  }
})
// 计算文件上传状态
const computeProgress = (uid) => {
  let uploadColor = "#409eff"
  uploadPercentList.value.forEach(item => {
    if (item.fileUid === uid) {
      if (item.percent === 200) {
        uploadColor = "#67c23a"
      }
    }
  })
  return uploadColor;
}

//文件后缀与图标map
const fileTypeMap = useComputedFileType()

const inputText = computed(() => {
  return fileList.value.length !== 0 ? '已选择' + fileList.value.length + '个文件' : '请选择文件'
})

// 文件预览地址
const previewUrl = ref('')
const visible = ref(false)
// 监听文件目录类型变化
watchEffect(() => {
  fileType.value = props.type
  if (props.type === '') {
    fileList.value = []
    uploadingNum.value = 0
  }
})
// mounted生命周期执行逻辑
onMounted(() => {
  //绑定拖拽监听器
  if (drag.value) {
    drag.value.addEventListener('drop', handleDrop)
    drag.value.addEventListener('dragleave', (e) => {
      e.preventDefault()
    })
    drag.value.addEventListener('dragenter', (e) => {
      e.preventDefault()
    })
    drag.value.addEventListener('dragover', (e) => {
      e.preventDefault()
    })

  }

  if (empty.value) {
    empty.value.addEventListener('drop', handleDrop)
    empty.value.addEventListener('dragleave', (e) => {
      e.preventDefault()
    })
    empty.value.addEventListener('dragenter', (e) => {
      e.preventDefault()
    })
    empty.value.addEventListener('dragover', (e) => {
      e.preventDefault()
    })
  }

})

onBeforeUnmount(() => {
  //销毁监听器
  if (drag.value) {
    drag.value.removeEventListener('drop', handleDrop)
  }
  if (empty.value) {
    empty.value.removeEventListener('drop', handleDrop)
  }
})
//按钮触发上传事件
const uploadFile = () => {
  fileRef.value.dispatchEvent(new MouseEvent('click'))
}

const closeImageDialog = () => {
  visible.value = false
}

const computeStatus = (status) => {
  if (status === 'success') {
    return {
      background: 'var(--el-color-success)',
    }
  } else {
    return {background: 'unset'}
  }
}

const computeFile = (file, event) => {
  let photoType = ['png', 'jpg', 'jpeg']
  let videoType = ['mp4', 'avi']
  if (event === 'noImage') {
    return photoType.indexOf(file?.name.substring(file?.name.lastIndexOf('.') + 1)?.toLowerCase()) === -1
  } else if (event === 'photoPreview') {
    return photoType.indexOf(file.name.substring(file.name.lastIndexOf('.') + 1)?.toLowerCase()) !== -1 && file.response
  } else if (event === 'videoPreview') {
    return videoType.indexOf(file.name.substring(file.name.lastIndexOf('.') + 1)?.toLowerCase()) !== -1 &&
        file.response
  } else if (event === 'photoUploading') {
    return photoType.indexOf(file.name.substring(file.name.lastIndexOf('.') + 1).toLowerCase()) !== -1
  }
}

// 文件上传中间逻辑
const processFileUpload = (list) => {
  for (var i = 0; i < list.length; i++) {
    let flag = false
    // 重复文件判断
    fileList.value.forEach(item => {
      if (item.name === list[i].name) {
        ElMessage({
          message: '[ ' + item.name + ' ]' + ' 为重复文件',
          type: 'error',
        })
        flag = true
      }
    })
    // 文件大小限制判断
    if (list[i].size > uploadSize.value * 1024 * 1024) {
      ElMessage({
        message: '[ ' + list[i].name + ' ]' + ' 超过文件限制大小(' + uploadSize.value + 'M )',
        type: 'error',
      })
      continue
    }
    if (!flag) {
      upload.value.handleStart(list[i])
    }
  }
}
// 点击上传触发逻辑
const fileChange = (e) => {
  //手动上传文件
  let list = e.target.files
  processFileUpload(list)
  upload.value.submit()
  fileRef.value.value = ''
}

//拖拽上传文件
const handleDrop = (e) => {
  e.preventDefault()
  let list = e.dataTransfer.files
  processFileUpload(list)
  upload.value.submit()
}
// 文件清空
const clearFiles = () => {
  let obj = {
    file: null,
    allRemoveFlag: true
  }
  uploadingNum.value = 0
  emit('removeUpload', obj)
  // el-upload组件外部方法执行
  upload.value.clearFiles()
}

// 文件上传前使 正在上传的文件数量+1
const handleBeforeUpload = () => {
  uploadingNum.value = uploadingNum.value + 1
}

// 移除文件逻辑
const handleRemove = (file) => {
  fileList.value = fileList.value.filter((item) => {
    return item["name"] !== file.name
  })
  if (uploadingNum.value > 0) {
    // 正在上传文件数量-1
    uploadingNum.value = uploadingNum.value - 1
  }
  let obj = {
    file: file,
    allRemoveFlag: false
  }
  emit('removeUpload', obj)
  if (uploadingNum.value === 0 && fileList.value.length !== 0) {
    emit('allUploadSuccess')
  }
}
// 上传成功后的逻辑 参数：(response上传请求返回结果,uploadFile当前文件,uploadFiles上传文件列表)
const handleSuccess = () => {

}
// 执行文件预览逻辑
const handlePreview = async (fileUrl, fileType) => {
  previewType.value = fileType
  previewUrl.value = fileUrl
  await nextTick()
  visible.value = true
}

// 上传请求入口
const handleHttpRequest = async ({file, onProgress, onError}) => {
  emit('onProgress')
  try {
    // 文件大小限制
    if (file.size < uploadSize.value * 1024 * 1024) {
      let hasTimer = false
      let hasCompletePreUpload = false
      const res = await request({
        url: '/admin/front/upload',
        method: 'post',
        data: {
          file
        },
        timeout: 600000,
        headers: {
          //上传携带请求头
          Authorization: 'Bearer ' + getToken(),
          'Content-Type': 'multipart/form-data'
        },
        // 上传时赋予进度值
        onUploadProgress: progressEvent => {
          const complete = Number(
              ((progressEvent.loaded / progressEvent.total) * 100) | 0)
          // 当前上传进度
          let currentUploadPercent = null
          // 判断是否已经存在定时器
          uploadTimerList.value.forEach(item => {
            if (item.uid === file.uid) {
              hasTimer = true
            }
          })
          // 判断是否已经存在上传进度
          uploadPercentList.value.forEach(item => {
            if (item.fileUid === file.uid) {
              hasCompletePreUpload = true
              currentUploadPercent = item
            }
          })
          // 不存在上传进度则添加
          if (!hasCompletePreUpload) {
            uploadPercentList.value.push({fileUid: file.uid, percent: complete})
          } else {
            // 存在上传进度则更新
            currentUploadPercent.percent = complete
          }
          // 不存在定时器则创建
          if (complete === 100 && !hasTimer) {
            // 创建定时器，模拟上传进度
            createTimer(file.uid, file.size / 1024 / 1024 / 0.4)
          }
          // 上传进度回调
          onProgress({percent: complete})
        }
      })
      if (uploadingNum.value > 0) {
        // 上传成功后拉满进度条
        uploadPercentList.value.forEach(item => {
          if (item.fileUid === file.uid) {
            item.percent = 200
          }
        })
        // 清除定时器
        uploadTimerList.value.forEach(item => {
          if (item.uid === file.uid) {
            clearInterval(item.timer)
          }
        })
        // 数量-1
        uploadingNum.value = uploadingNum.value - 1
        emit('uploadSuccess', res.data)
        if (uploadingNum.value === 0) {
          emit('allUploadSuccess')
        }
      }
      return res
    }
  } catch (e) {
    onError()
    uploadingNum.value = uploadingNum.value - 1
    return e
  }
}

</script>
<style lang="scss">

.upload-demo {
  --el-color-primary: none;
  width: 100%;
}

.upload-list__item-thumbnail {
  object-fit: contain;
  height: 60% !important;
}

.file-name-text {
  text-overflow: ellipsis;
  overflow: hidden;
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  position: absolute;
  top: 66%;
  right: 10%;
  left: 10%;

}

.imageType {
  width: 100% !important;
  position: absolute;
  left: 0%;
  top: 10%;
  height: 40% !important;
}

.noImageType {
  width: 60% !important;
}

.upload--button {
  margin-left: 2rem;
  font-size: 1rem;
  background: #00A298 !important;
  color: #ffffff !important;
}

.el-upload-list {
  padding: 0;
  list-style: none;
  position: relative;
}

.text-ellipsis {
  white-space: nowrap;
  text-overflow: ellipsis;
  overflow: hidden;
}

.upload-list__item {
  width: 17rem !important;
  height: 27rem !important;
  justify-content: center !important;
  overflow: hidden;
  background-color: var(--el-fill-color-blank);
  border: 0.1rem solid var(--el-border-color);
  border-radius: 6px;
  box-sizing: border-box;
  padding: 0;
  display: inline-flex;
}

@keyframes loading {
  to {
    transform: rotate(1turn);
  }
}


.el-icon--upload {
  margin-top: 4rem;
  font-size: 8rem !important;
  color: #909399 !important;
}

.el-upload-list--picture-card {
  padding: 2rem;
  border: 2px dashed var(--el-text-color-placeholder);
  width: 95%;
  display: flex;
  flex-wrap: wrap;
  margin: auto;
  row-gap: 1rem;
  column-gap: 1rem;
  min-height: 24rem;
  height: 100%;
}

.empty-file {
  text-align: center;
  min-height: 27rem;
  margin: auto;
  padding: 2rem;
  width: 95%;
  border: 2px dashed var(--el-text-color-placeholder);

}

.el-upload__text {
  color: #909399 !important;
  font-size: 1.7rem !important;
}

.file-size {
  position: absolute;
  bottom: 0px;
  width: 100%;
  left: 0;
  right: 0;
  margin-bottom: 1rem;
}

.upload-template {
  text-align: center;
  width: 100%;
  height: 100%;
  position: relative;
}

.upload-text {
  margin-top: -1rem;
  font-size: 1.4rem;
  line-height: 2rem;
  width: 70%;
  margin-left: 2.5rem;
  color: #8E8E8E !important;
}

.upload-list__item-status-label i {
  font-size: 15px;
  margin-top: 9px;
  width: 20px !important;
  height: 20px !important;
  justify-content: flex-end !important;
  transform: rotate(-45deg);
}


.upload-list__item-status-label {
  right: -18px;
  top: -6px;
  width: 58px;
  height: 29px;
  display: block !important;
  text-align: center;
  transform: rotate(45deg);
}

.el-upload-list--text {
  display: none !important;
}

.el-upload--text {
  display: block;
}

.upload-input {
  width: 99%;
  display: inline-flex;
  margin-bottom: 2rem;
}


.upload-list__item-preview {
  display: inline-flex;
}

.upload-list__item-delete {
  display: inline-flex;
}

.upload-list__item-status-label {
  position: absolute;
  right: 5px;
  top: 0;
  line-height: inherit;
  display: none;
  height: 100%;
  justify-content: center;
  align-items: center;
  transition: opacity var(--el-transition-duration);
}

.upload-list__item.is-success .upload-list__item-status-label {
  display: inline-flex;
}

.upload-list__item.is-success:focus .upload-list__item-status-label {
  display: none;
  opacity: 0;
}

.upload-list__item-status-label {
  right: -18px;
  top: -6px;
  width: 58px;
  height: 29px;
  background: var(--el-color-success);
  text-align: center;
  transform: rotate(45deg);
}

.el-icon--check, .el-icon--circle-check {
  color: #fff;
}

.el-upload-list--picture-card .el-upload-list__item-actions {
  font-size: 4rem !important;
}

.el-empty__image {
  width: 30% !important;
}

.el-empty__description p {
  font-size: 1rem !important;
}

.el-empty {
  padding: 0 !important;
  margin-top: 1rem;
}

.progress-text {
  position: absolute;
  width: 100%;
  top: 64%;
}

.percentage-value {
  display: block;
  font-size: 2rem;
  top: 50%;
}

.percentage-label {
  display: block;
  font-size: 1rem;
  margin-top: 1rem;
}

.el-upload-list--picture-card .el-progress .el-progress__text {
  top: 50%;
  height: 100% !important;
}

.el-progress-circle {
  height: 16rem !important;
  width: 16rem !important;
}

.bounce-enter-active {
  animation: bounce-in 0.5s;
}

.bounce-leave-active {
  animation: bounce-in 0.5s reverse;
}

@keyframes bounce-in {
  0% {
    transform: scale(0);
  }
  100% {
    transform: scale(1);
  }
}

.bounce2-enter-active {
  animation: bounce-in 0.4s;
}

.progress {
  top: 57% !important;
  position: absolute;
  left: 50%;
  width: 14rem !important;
}

.progress-complete {
  position: absolute;
  top: 54%;
  right: 33%;
  left: 33%;
  height: 2rem;
}
</style>
